﻿using System;
using System.Collections.Generic;
using System.Linq;
using PSE.Updater.Common.Configurations.Server;
using PSE.Updater.Common.Gateway.DataContract;
using PSE.Updater.Common.Providers;
using PSE.Updater.Common.Providers.Interfaces;
using Configuration = PSE.Updater.Common.Configurations.Server;
using PSE.Framework.ErrorLogging;
using Microsoft.Win32;

namespace PSE.Updater.Server.Engine
{
    /// <summary>
    /// Classe que adminsitra o Serviço de Atualizações, iniciando e finalizando os watchers(monitores de pasta) e listando as atualizações disponíveis
    /// </summary>
    public class UpdateService
    {
        private const string CONST_UPDATES_SYSTEM = "PSE";
        private const string CONST_UPDATES_FOLDER = "Updates";
        private const string CONST_UPDATES_EXTENSION = "*.exe";

        private const string CONST_REG_SYSTEM = @"SOFTWARE\" + CONST_UPDATES_SYSTEM;
        private const string CONST_REG_APPLICATION_ROOT_DIRECTORY = "ApplicationRootDirectory";

        #region [ Singleton ]
        private static object _singletonLock = new object();
        private static UpdateService _instance;
        private UpdateService() { }

        public static UpdateService Instance
        {
            get
            {
                lock (_singletonLock)
                {
                    if (_instance == null)
                        _instance = new UpdateService();

                    return _instance;
                }
            }
        }
        #endregion [ Singleton ]

        private List<Watcher> _watchers = new List<Watcher>();

        /// <summary>
        /// Inicializa o Serviço de Atualização
        /// Ao iniciar, o serviço carrega todos os watchers(monitores de pasta) adicionando as informações no cache.
        /// </summary>
        public void Initialize()
        {
            try
            {
                Terminate();

                //DebugLog.Write("Obtendo as informações do .config pro grupo updateProviders");
                //Configuration.ProductConfigurationSection section = Configuration.UpdateProviders.Instance.GetSection();

                //DebugLog.Write("Obtendo os produtos instalados");
                //Configuration.ProductConfigurationElementCollection products = section.Products;

                //foreach (Configuration.ProductConfigurationElement product in products)
                //{
                //    foreach (Configuration.ProviderConfigurationElement provider in product.Providers)
                //    {
                //        foreach (WatcherConfigurationElement item in provider.Watchers)
                //        {
                //            DebugLog.Write("Criando o Watcher");
                //            Watcher watcher = new Watcher();
                //            watcher.Start(item, product.Name, provider.Type);
                //            _watchers.Add(watcher);
                //        }
                //    }
                //}

                //DebugLog.Write(_watchers.Count.ToString() + " watchers adicionados...");

                string installationPath = GetInstallationPath();

                Watcher watcher = new Watcher();
                watcher.Start(new Watcher.Options
                {
                    Directory = System.IO.Path.Combine(installationPath, CONST_UPDATES_FOLDER),
                    ExtensionsFilter = CONST_UPDATES_EXTENSION
                }, ProviderType.Updates);

                _watchers.Add(watcher);
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
            }
        }

        private string GetInstallationPath()
        {
            var key = Registry.LocalMachine.OpenSubKey(CONST_REG_SYSTEM);
            string installationPath = key.GetValue(CONST_REG_APPLICATION_ROOT_DIRECTORY, string.Empty).ToString();

            installationPath = installationPath.Replace(CONST_UPDATES_SYSTEM, string.Empty);

            return installationPath;
        }

        /// <summary>
        /// Obtem as atualizações disponíveis por provider
        /// </summary>
        /// <param name="provider">Tipo do Provider</param>
        /// <param name="moduleVersions">Conjunto de Módulos</param>
        /// <returns>Atualizações disponíveis por provider</returns>
        public IList<IFileDetails> GetAvailableUpdates(ProviderType provider, List<ModuleVersion> moduleVersions)
        {
            IUpdateDiscovery updateDiscovery = ProviderFactory.Instance.GetUpdateDiscovery(provider);
            return updateDiscovery.GetAvailableUpdates(provider, moduleVersions);
        }

        /// <summary>
        /// Obtem as atualizações disponíveis por provider
        /// </summary>
        /// <param name="provider">Tipo do Provider</param>
        /// <param name="module">Módulo a ser pesquisado</param>
        /// <returns>Atualizações disponíveis por provider</returns>
        public IFileDetails GetAvailableUpdate(ProviderType provider, ModuleVersion module)
        {
            if (module == null)
            {
                Exception ex = new ArgumentNullException("module");
                ErrorLogger.WriteLog(LogType.Error, ex);
                throw ex;
            }

            List<ModuleVersion> moduleVersions = new List<ModuleVersion>();
            moduleVersions.Add(module);

            IUpdateDiscovery updateDiscovery = ProviderFactory.Instance.GetUpdateDiscovery(provider);
            var updates = updateDiscovery.GetAvailableUpdates(provider, moduleVersions);
            if (updates != null && updates.Count > 0)
                return updates.FirstOrDefault();

            return null;
        }

        public IFileDetails GetAvaliableFile(ProviderType provider, ModuleVersion module)
        {
            if (module == null)
            {
                Exception ex = new ArgumentNullException("module");
                ErrorLogger.WriteLog(LogType.Error, ex);
                throw ex;
            }

            IUpdateDiscovery updateDiscovery = ProviderFactory.Instance.GetUpdateDiscovery(provider);
            var files = updateDiscovery.GetAllFiles(module.SystemName);
            var filteredFiles = from f in files.Where(x => x.ProviderType == provider)
                                where f.SystemName.Equals(module.SystemName) &
                                      f.Version.Equals(module.Version) &
                                      f.ModuleName.Equals(module.ModuleName)
                                select f;
            var file = filteredFiles.FirstOrDefault();

            return file;
        }

        /// <summary>
        /// Lista todos os Arquivos disponíveis por Provider
        /// </summary>
        /// <param name="provider">Provide</param>
        /// <returns>Todos os Arquivos disponíveis por Provider</returns>
        public IList<IFileDetails> ListFiles(ProviderType provider)
        {
            List<IFileDetails> files = new List<IFileDetails>();

            var watcher = _watchers.FirstOrDefault(w => w.ProviderType == provider);
            if (watcher != null)
                files.AddRange(watcher.ListFileDetails());

            return files;
        }

        /// <summary>
        /// Lista todos os Arquivos disponíveis por Provider e Produto
        /// </summary>
        /// <param name="provider">Provide</param>
        /// <param name="productName">Nome do Produto</param>
        /// <returns>Todos os Arquivos disponíveis por Provider e Produto</returns>
        public IList<IFileDetails> ListFiles(ProviderType provider, string productName)
        {
            List<IFileDetails> files = new List<IFileDetails>();

            var watcher = _watchers.FirstOrDefault(w => w.ProviderType == provider);
            if (watcher != null)
            {
                IList<IFileDetails> file = watcher.ListFileDetails(productName);
                if (file != null)
                {
                    files.AddRange(file);
                }
                else
                {
                    ErrorLogger.WriteLog(LogType.Information, string.Format("Nenhum arquivo encontrado para o produto {0}", productName));
                }
            }
            return files;
        }

        /// <summary>
        /// Finaliza o Serviço de Atualização, removendo os watchers(monitores de pasta)
        /// </summary>
        public void Terminate()
        {
            for (int i = _watchers.Count - 1; i >= 0; i--)
            {
                _watchers[i].Stop();
                _watchers.RemoveAt(i);
            }
        }
    }
}
